
在考虑JIT编译器时，首先想到的是直接运行LLVM IR。这就是lli工具、LLVM解释器和动态编译器所做的工作。我们将在下一节中探索lli工具，然后实现一个类似的工具。\par

\hspace*{\fill} \par %插入空行
\textbf{探索lli工具}

让我们用一个非常简单的例子来试试lli工具。将以下源代码存储为hello.ll文件，它相当于C程序的hello world，其声明了来自C库的printf()函数，hellostr常量包含要打印的消息。在main()函数内部，通过getelementptr指令计算消息的第一个字符的指针，并将该值传递给printf()函数，程序总是返回0。完整的源代码如下:\par

\begin{tcolorbox}[colback=white,colframe=black]
declare i32 @printf(i8*, ...) \\
\\
@hellostr = private unnamed\underline{~}addr constant [13 x i8] c"Hello  \\
\hspace*{8cm}world$\setminus$0A$\setminus$00" \\
\\
define i32 @main(i32 \%argc, i8** \%argv) \{ \\
\hspace*{0.5cm}\%res = call i32 (i8*, ...) @printf( \\
\hspace*{3.5cm}i8* getelementptr inbounds ([13 x i8], \\
\hspace*{4.5cm}[13 x i8]* @hellostr, i64 0, i64 0)) \\
\hspace*{0.5cm}ret i32 0
\}
\end{tcolorbox}

这个LLVM IR文件是通用的，对所有平台都有效。我们可以通过以下命令直接使用lli工具执行IR:\par

\begin{tcolorbox}[colback=white,colframe=black]
\$ lli hello.ll \\
Hello world
\end{tcolorbox}

这里有趣的一点是如何找到printf()函数。IR代码编译为机器码，并触发对printf符号的查找。这个符号在IR中找不到，所以当前进程会搜索它。lli工具动态链接C库，可以在那里找到符号。\par

当然，lli工具不会链接您创建的库。为了使用这些函数，lli工具支持加载共享库和对象。下面的C源代码只打印了一个友好的消息:\par

\begin{lstlisting}[caption={}]
#include <stdio.h>

void greetings() {
	puts("Hi!");
}
\end{lstlisting}

存储在greeting.c文件中，我们使用它来使用lli工具探索对象的加载。将此源代码编译为动态库。-fPIC选项指示clang生成位置无关的代码，这是动态库所必需的。通过给定的-shared选项，编译器创建greetings.so动态库:\par

\begin{tcolorbox}[colback=white,colframe=black]
\$ clang –fPIC –shared –o greetings.so greetings.c
\end{tcolorbox}

我们还将该文件编译为一个greetings.o对象文件:\par

\begin{tcolorbox}[colback=white,colframe=black]
\$ clang –c –o greetings.o greetings.c
\end{tcolorbox}

我们现在有两个文件，greetings.so和greetings.o文件，将他们加载到lli工具中。\par

我们还需要一个LLVM IR文件，它调用greetings()函数。为此，创建main.ll文件，其中包含对函数的单独调用:\par

\begin{tcolorbox}[colback=white,colframe=black]
declare void @greetings(...) \\
\\
define dso\underline{~}local i32 @main(i32 \%argc, i8** \%argv) \{ \\
\hspace*{0.5cm}call void (...) @greetings() \\
\hspace*{0.5cm}ret i32 0 \\
\}
\end{tcolorbox}

如果尝试像以前一样执行IR，那么lli工具无法定位greetings符号，就会崩溃:\par

\begin{tcolorbox}[colback=white,colframe=black]
\$ lli main.ll \\
PLEASE submit a bug report to https://bugs.llvm.org/ and \\
include the crash backtrace.
\end{tcolorbox}

greeting()函数定义在一个外部文件中，为了修复崩溃，必须告诉lli工具需要加载哪个文件。为了使用动态库，必须使用-load选项，它将动态库的路径作为参数:\par

\begin{tcolorbox}[colback=white,colframe=black]
\$ lli –load ./greetings.so main.ll \\
Hi!
\end{tcolorbox}

如果包含动态库的目录不在动态加载器的搜索路径中，那么指定动态库的路径是很重要的。如果省略，则将找不到库。\par

或者，可以使用-extrobject选项来指示lli工具加载的对象文件:\par

\begin{tcolorbox}[colback=white,colframe=black]
\$ lli –extra-object greetings.o main.ll \\
Hi!
\end{tcolorbox}

其他支持的选项是-extra-archive(加载静态库文件)，和-extramodule(加载比特码文件)。这两个选项都要求将文件路径作为参数。\par

现在，知道了如何使用lli工具直接执行LLVM IR。在下一节中，将实现自己的JIT工具。\par

\hspace*{\fill} \par %插入空行
\textbf{用LLJIT实现自己的JIT编译器}

lli工具只不过是LLVM API的一个包装器。在之前的内容中，我们了解到ORC引擎使用分层方法，ExecutionSession类表示一个正在运行的JIT程序。除了其他项外，这个类还保存使用的JITDylib实例。JITDylib实例是一个符号表，将符号名映射到地址，例如：可以是LLVM IR文件中定义的符号，也可以是加载的动态库的符号。\par

要执行LLVM IR，不需要自己创建一个JIT堆栈，LLJIT类提供了这个功能。您还可以在从旧的MCJIT实现迁移时使用这个类，这个类本质上提供了相同的功能。在下一小节中，我们将从JIT引擎的初始化开始实现。\par

\hspace*{\fill} \par %插入空行
\textbf{为编译LLVM IR初始化JIT引擎}

我们首先实现设置JIT引擎的函数，编译LLVM IR模块，并在该模块中执行main()函数。稍后，我们将使用这个核心功能构建一个小型JIT工具。先是jitmain()函数:\par

\begin{enumerate}
\item 该函数需要带有IR的LLVM模块来执行。这个模块还需要LLVM context类，因为context类包含重要的类型信息。我们的目标是调用main()函数，所以也传递了argc和argv参数:
\begin{lstlisting}[caption={}]
Error jitmain(std::unique_ptr<Module> M,
			  std::unique_ptr<LLVMContext> Ctx, int 
			  argc,
			  char *argv[]) {
\end{lstlisting}

\item 我们使用LLJITBuilder类创建一个LLJIT实例。如果发生错误，则返回错误。一个可能的错误原因是平台还不支持JIT编译:
\begin{lstlisting}[caption={}]
	auto JIT = orc::LLJITBuilder().create();
	if (!JIT)
		return JIT.takeError();
\end{lstlisting}

\item 然后将模块添加到主JITDylib实例中。如果配置了，那么JIT编译可以使用多个线程。因此，需要将模块和上下文包装在ThreadSafeModule实例中。如果发生错误，则返回错误:
\begin{lstlisting}[caption={}]
	if (auto Err = (*JIT)->addIRModule(
			orc::ThreadSafeModule(std::move(M),
								  std::move(Ctx))))
		return Err;
\end{lstlisting}

\item 与lli工具一样，我们也支持来自C库的符号。DefinitionGenerator类公开符号，DynamicLibrary\allowbreak SearchGenerator子类公开在动态库中找到的名称。这个类提供了两个工厂方法：Load()方法可用于加载动态库，而GetForCurrentProcess()方法公开当前进程的符号。这里，我们使用后一个函数。符号名可以有前缀，这取决于平台。我们检索数据布局并将前缀传递给GetForCurrentprocess()函数。然后以正确的方式处理符号名，我们不需要关心它。像往常一样，如果发生错误，则返回函数:
\begin{lstlisting}[caption={}]
	const DataLayout &DL = (*JIT)->getDataLayout();
	auto DLSG = orc::DynamicLibrarySearchGenerator::
		GetForCurrentProcess(DL.getGlobalPrefix());
	if (!DLSG)
		return DLSG.takeError();
\end{lstlisting}

\item 然后我们将生成器添加到主JITDylib实例中。如果需要查找一个符号，也会搜索加载的动态库中的符号:
\begin{lstlisting}[caption={}]
	(*JIT)->getMainJITDylib().addGenerator(
		std::move(*DLSG));
\end{lstlisting}

\item 接下来，我们需要查找主符号。这个符号必须在命令行中给出的IR模块中，查找触发IR模块的编译。如果IR模块中引用了其他符号，则使用前一步中添加的生成器解析它们。结果类型是JITEvaluatedSymbol类:
\begin{lstlisting}[caption={}]
	auto MainSym = (*JIT)->lookup("main");
	if (!MainSym)
		return MainSym.takeError();
\end{lstlisting}

\item 我们向返回的JIT符号询问函数的地址，并将这个地址转换为C main()函数的原型:
\begin{lstlisting}[caption={}]
auto *Main = (int (*)(
	int, char **))MainSym->getAddress();
\end{lstlisting}

\item 现在我们可以调用IR模块中的main()函数，并传递argc和argv参数。这里，忽略返回值:
\begin{lstlisting}[caption={}]
	(void)Main(argc, argv);
\end{lstlisting}

\item 报告函数确定函数是否执行成功:
\begin{lstlisting}[caption={}]
	return Error::success();
}
\end{lstlisting}

\end{enumerate}

使用JIT编译还挺容易。除了公开当前进程的符号或动然库中的符号外，还有许多其他的方法可以公开名称。StaticLibraryDefinitionGenerator类公开在静态归档文件中找到的符号，可以像DynamicLibrarySearchGenerator类一样使用。LLJIT类还有一个addObjectFile()方法来公开对象文件的符号。如果现有的实现不能满足您的需要，还可以提供自己的DefinitionGenerator实现。在下一小节中，将实现扩展到JIT编译器中。\par

\hspace*{\fill} \par %插入空行
\textbf{创建JIT编译器工具}

jitmain()函数可以很容易地扩展为一个小工具。源代码保存在JIT.cpp文件中，是一个简单的JIT编译器:\par

\begin{enumerate}
\item 我们必须包含几个头文件LLJIT.h头文件定义了LLJIT类和ORC API的核心类。IRReader.h头文件，定义了一个读取LLVM IR文件的函数。CommandLine.h允许以LLVM风格解析命令行选项。最后，InitLLVM.h用于工具的基本初始化，TargetSelect.h用于初始化本机目标:
\begin{lstlisting}[caption={}]
#include "llvm/ExecutionEngine/Orc/LLJIT.h"
#include "llvm/IRReader/IRReader.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/TargetSelect.h"
\end{lstlisting}

\item 将llvm命名空间添加到当前作用域:
\begin{lstlisting}[caption={}]
using namespace llvm;
\end{lstlisting}

\item 我们的JIT工具只需要一个输入文件，用cl::opt<>类声明它:
\begin{lstlisting}[caption={}]
static cl::opt<std::string>
	InputFile(cl::Positional, cl::Required,
		cl::desc("<input-file>"));
\end{lstlisting}

\item 要读取IR文件，调用parseIRFile()函数。该文件可以是文本IR，也可以是位码文件，函数返回指向所创建模块的指针。错误处理略有不同，因为文本IR文件可以解析，但语法不一定正确。SMDiagnostic实例保存语法错误时的错误信息，输出错误消息，并退出应用程序:
\begin{lstlisting}[caption={}]
std::unique_ptr<Module>
loadModule(StringRef Filename, LLVMContext &Ctx,
			const char *ProgName) {
	SMDiagnostic Err;
	std::unique_ptr<Module> Mod =
		parseIRFile(Filename, Err, Ctx);
	if (!Mod.get()) {
		Err.print(ProgName, errs());
		exit(-1);
	}
	return std::move(Mod);
}
\end{lstlisting}

\item jitmain()函数放在这里:
\begin{lstlisting}[caption={}]
Error jitmain(…) { … }
\end{lstlisting}

\item 然后添加main()函数，它初始化工具和本机目标，并解析命令行:
\begin{lstlisting}[caption={}]
int main(int argc, char *argv[]) {
	InitLLVM X(argc, argv);
	
	InitializeNativeTarget();
	InitializeNativeTargetAsmPrinter();
	InitializeNativeTargetAsmParser();
	
	cl::ParseCommandLineOptions(argc, argv,
	     						"JIT\n");
\end{lstlisting}

\item 接下来，初始化LLVM context类:
\begin{lstlisting}[caption={}]
	auto Ctx = std::make_unique<LLVMContext>();
\end{lstlisting}

\item 然后加载命令行中命名的IR模块:
\begin{lstlisting}[caption={}]
	std::unique_ptr<Module> M =
		loadModule(InputFile, *Ctx, argv[0]);
\end{lstlisting}

\item 然后可以调用jitmain()函数，我们使用ExitOnError实用程序类处理错误。当发生错误时，该类打印错误消息并退出应用程序。还可以设置了一个带有应用程序名称的标识，它会将之前的错误消息打印出来:
\begin{lstlisting}[caption={}]
	ExitOnError ExitOnErr(std::string(argv[0]) + ": ");
	ExitOnErr(jitmain(std::move(M), std::move(Ctx),
					  argc, argv));
\end{lstlisting}

\item 如果控制流到达此处，则成功执行了IR。返回0就表示成功:
\begin{lstlisting}[caption={}]
	return 0;
}
\end{lstlisting}

\end{enumerate}

这已经是完整的实现了!我们只需要添加构建描述。 \par

\hspace*{\fill} \par %插入空行
\textbf{添加CMake构建}

为了编译这个源文件，我们还需要创建一个带有构建描述的CMakeLists.txt文件，它保存在JIT.cpp文件之外:\par

\begin{enumerate}
\item 我们将最低要求的CMake版本设置为LLVM所需的版本，并将项目命名为jit:
\begin{tcolorbox}[colback=white,colframe=black]
cmake\underline{~}minimum\underline{~}required (VERSION 3.13.4) \\
project ("jit")
\end{tcolorbox}

\item 需要加载LLVM包，将LLVM提供的CMake模块目录添加到搜索路径中。然后包含Choose\allowbreak MSVCCRT模块，确保与LLVM使用的C运行时相同:
\begin{tcolorbox}[colback=white,colframe=black]
find\underline{~}package(LLVM REQUIRED CONFIG) \\
list(APPEND CMAKE\underline{~}MODULE\underline{~}PATH \$\{LLVM\underline{~}DIR\}) \\
include(ChooseMSVCCRT)
\end{tcolorbox}

\item 还需要从LLVM添加定义和包含路径。使用的LLVM组件通过函数调用映射到库名:
\begin{tcolorbox}[colback=white,colframe=black]
add\underline{~}definitions(\$\{LLVM\underline{~}DEFINITIONS\}) \\
include\underline{~}directories(SYSTEM \$\{LLVM\underline{~}INCLUDE\underline{~}DIRS\}) \\
llvm\underline{~}map\underline{~}components\underline{~}to\underline{~}libnames(llvm\underline{~}libs Core OrcJIT \\
\hspace*{6cm}Support  \\
\hspace*{6cm}native)
\end{tcolorbox}

\item 最后，定义可执行文件的名称、要编译的源文件和要链接的库:
\begin{tcolorbox}[colback=white,colframe=black]
add\underline{~}executable(JIT JIT.cpp) \\
target\underline{~}link\underline{~}libraries(JIT \$\{llvm\underline{~}libs\})
\end{tcolorbox}

\item 这就是JIT工具所需要的一切。创建并更改到build目录，然后运行以下命令来创建和编译应用程序:
\begin{tcolorbox}[colback=white,colframe=black]
\$ cmake –G Ninja <path to source directory> \\
\$ ninja
\end{tcolorbox}

\end{enumerate}

这将用来编译JIT工具，并且可以用hello.ll检查工具的功能:\par

\begin{tcolorbox}[colback=white,colframe=black]
\$ JIT hello.ll \\
Hello world
\end{tcolorbox}

创建JIT编译器就是这么简单!\par

该示例使用LLVM IR作为输入，但这不是必需的。LLJIT类使用ircompillayer类，负责将IR编译成机器码。您可以定义自己的层，可以接受需要的输入，例如Java字节码。\par

使用预定义的LLJIT类很方便，但是限制了灵活性。下一节中，我们将讨论如何使用ORC API提供的层来实现JIT编译器.\par

\hspace*{\fill} \par %插入空行
\textbf{从头开始构建JIT编译器类}

使用ORC的分层方法，可以很容易地构建一个定制的JIT编译器。没有适合所有人的JIT编译器，本章的第一节给出了一些示例。让我们看看如何设置JIT编译器。\par

ORC API将这些层是堆叠在一起的。最低的一层是对象链接层，由llvm::orc::RTDyldObject\allowbreak LinkingLayer类表示，负责链接内存中的对象并将它们转换为可执行代码。此任务所需的内存由MemoryManager接口的一个实例管理，有一个默认实现，如果需要，也可以使用自定义版本。\par

在对象链接层之上是编译层，它负责创建内存中的对象文件。llvm::orc::IRCompileLayer类将一个IR模块作为输入，并将其编译为对象文件。IRCompileLayer类是IRLayer类的子类，IRLayer类是一个通用类，用于接受LLVM IR的层实现。\par

这两层已经构成了JIT编译器的核心。添加一个LLVM IR模块作为输入，在内存中编译和链接。为了添加更多的功能，可以在它们之上添加更多的层，例如：CompileOnDemandLayer类拆分一个模块，以便只编译被请求的函数。这可以用来实现延迟编译。CompileOnDemandLayer类也是IRLayer类的子类。以一种非常通用的方式，IRTransformLayer类(也是IRLayer类的子类)允许我们对模块应用转换。\par

另一个重要的类是ExecutionSession类，这个类表示一个正在运行的JIT程序。基本上，这意味着该类管理JITDylib符号表，提供符号查找功能，并跟踪所使用的资源管理器。\par

JIT编译器的一般流程如下:\par

\begin{enumerate}
\item 初始化ExecutionSession类的实例。
\item 初始化层，至少包括RTDyldObjectLinkingLayer类和IRCompileLayer类。
\item 创建第一个JITDylib符号表，通常使用main或类似的名称。
\\它的用法与上一节提到的LLJIT类非常相似:
\item 将IR模块添加到符号表中。
\item 查找一个符号，触发关联函数的编译，可能还有整个模块。
\item 执行函数。
\end{enumerate}

在下一小节中，我们将基于泛型配置实现一个JIT编译器类。\par

\hspace*{\fill} \par %插入空行
\textbf{创建JIT编译器类}

为了保持JIT编译器类的实现简单，我们将所有内容都放到JIT.h头文件中。类的初始化稍微复杂一些。由于要处理错误，需要一个工厂方法在调用构造函数之前预先创建一些对象。创建类的步骤如下:\par

\begin{enumerate}
\item 首先，使用JIT\underline{~}H预处理器定义来保护头文件不被重复包含:
\begin{lstlisting}[caption={}]
#ifndef JIT_H
#define JIT_H
\end{lstlisting}

\item 需要一堆包含文件。它们中的大多数都提供了与头文件同名的类。Core.h头文件提供了两个基本类，包括ExecutionSession类。ExecutionUtils.h头文件提供了DynamicLibrarySearch\allowbreak Generator类来搜索库中的符号，我们已经在使用LLJIT实现我们自己的JIT编译器部分使用过。CompileUtils.h头文件提供了ConcurrentIRCompiler类:
\begin{lstlisting}[caption={}]
#include "llvm/Analysis/AliasAnalysis.h"
#include "llvm/ExecutionEngine/JITSymbol.h"
#include "llvm/ExecutionEngine/Orc/CompileUtils.h"
#include "llvm/ExecutionEngine/Orc/Core.h"
#include "llvm/ExecutionEngine/Orc/ExecutionUtils.h"
#include "llvm/ExecutionEngine/Orc/IRCompileLayer.h"
#include "llvm/ExecutionEngine/Orc/IRTransformLayer.h"
#include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h"
#include "llvm/ExecutionEngine/Orc/Mangling.h"
#include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h"
#include "llvm/ExecutionEngine/Orc/TargetProcessControl.h"
#include "llvm/ExecutionEngine/SectionMemoryManager.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Support/Error.h"
\end{lstlisting}

\item 我们的新类是JIT类:
\begin{lstlisting}[caption={}]
class JIT {
\end{lstlisting}

\item 私有数据成员为ORC层和一个助手类。ExecutionSession、ObjectLinkingLayer、CompileLayer、OptIRLayer和MainJITDylib实例表示正在运行的JIT程序、各层和符号表。TargetProcess\allowbreak Control实例用于与JIT目标流程进行交互，这可以是相同的进程，也可以是同一台机器上的另一个进程，或者是不同机器上的远程进程(可能具有不同的体系结构)。DataLayout和MangleAnd\allowbreak Interner类需要以正确的方式篡改符号名称。符号名是内化的，这意味着所有相等的名称都有相同的地址。为了检查两个符号名是否相等，比较地址就足够了，这是非常快速的操作:
\begin{lstlisting}[caption={}]
	std::unique_ptr<llvm::orc::TargetProcessControl> 
		TPC;
	std::unique_ptr<llvm::orc::ExecutionSession> ES;
	llvm::DataLayout DL;
	llvm::orc::MangleAndInterner Mangle;
	std::unique_ptr<llvm::orc::RTDyldObjectLinkingLayer>
		ObjectLinkingLayer;
	std::unique_ptr<llvm::orc::IRCompileLayer>
		CompileLayer;
	std::unique_ptr<llvm::orc::IRTransformLayer>
		OptIRLayer;
	llvm::orc::JITDylib &MainJITDylib;
\end{lstlisting}

\item 初始化分为三个部分。在C++中，构造函数不能返回错误。一个简单且推荐的解决方案是创建一个静态工厂方法，该方法可以在构造对象之前进行错误处理。层的初始化更加复杂，因此我们也为它们引入了工厂方法。\par
在create()工厂方法中，首先创建一个SymbolStringPool实例，该实例用于实现字符串内部化，并由几个类共享。为了控制当前进程，创建了一个SelfTargetProcessControlinstance。如果想要瞄准一个不同的流程，则需要更改这个实例。\par
然后，构造一个JITTargetMachineBuilder实例，为此需要知道JIT进程的目标三元组。接下来，我们向目标机器生成器查询数据布局，例如：如果构建器不能基于提供的三元组实例化目标机器，这个步骤可能会失败，因为对这个目标的支持没有编译到LLVM库中:
\begin{lstlisting}[caption={}]
public:
static llvm::Expected<std::unique_ptr<JIT>> create() {
	auto SSP =
		std::make_shared<llvm::orc::SymbolStringPool>();
	auto TPC =
		llvm::orc::SelfTargetProcessControl::Create(SSP);
	if (!TPC)
		return TPC.takeError();
	llvm::orc::JITTargetMachineBuilder JTMB(
		(*TPC)->getTargetTriple());
	auto DL = JTMB.getDefaultDataLayoutForTarget();
	if (!DL)
		return DL.takeError();
\end{lstlisting}

\item 至此，我们已经处理了所有可能失败的调用，现在能够初始化ExecutionSession实例。最后，使用所有实例化的对象调用JIT类的构造函数，并将结果返回给调用者:
\begin{lstlisting}[caption={}]
	auto ES =
		std::make_unique<llvm::orc::ExecutionSession>(
			std::move(SSP));
	
	return std::make_unique<JIT>(
		std::move(*TPC), std::move(ES),
		std::move(*DL),
		std::move(JTMB));
}
\end{lstlisting}

\item JIT类的构造函数将传递的参数移动到私有数据成员，层对象是通过调用带有create前缀的静态工厂名称来构造的。每个层工厂方法都需要对ExecutionSession实例的引用，将该层连接到正在运行的JIT会话。除了位于层栈底部的对象链接层外，每一层还需要参考上一层:
\begin{lstlisting}[caption={}]
	JIT(std::unique_ptr<llvm::orc::TargetProcessControl>
			TPCtrl,
	std::unique_ptr<llvm::orc::ExecutionSession> ExeS,
	llvm::DataLayout DataL,
	llvm::orc::JITTargetMachineBuilder JTMB)
	: TPC(std::move(TPCtrl)), ES(std::move(ExeS)),
			DL(std::move(DataL)), Mangle(*ES, DL),
		ObjectLinkingLayer(std::move(
			createObjectLinkingLayer(*ES, JTMB))),
		CompileLayer(std::move(createCompileLayer(
			*ES, *ObjectLinkingLayer,
			 std::move(JTMB)))),
		OptIRLayer(std::move(
			createOptIRLayer(*ES, *CompileLayer))),
		MainJITDylib(ES->createBareJITDylib("<main>")) {
\end{lstlisting}

\item 构造函数的主体中，我们添加了一个生成器来搜索当前进程中的符号。GetForCurrentProcess()方法是特殊的，因为返回值包装在一个Expected<>模板中，这表明也可以返回一个Error对象。但是我们知道不会发生错误，也就是当前进程最终会运行!因此，我们使用cantFail()函数展开结果，如果出现错误，该函数将终止应用程序:
\begin{lstlisting}[caption={}]
	MainJITDylib.addGenerator(llvm::cantFail(
		llvm::orc::DynamicLibrarySearchGenerator::
			GetForCurrentProcess(DL.getGlobalPrefix())));
}
\end{lstlisting}

\item 为了创建对象链接层，需要提供一个内存管理器。这里我们坚持使用默认的SectionMemory\allowbreak Manager类，但如果需要，也可以提供不同的实现:
\begin{lstlisting}[caption={}]
	static std::unique_ptr<
		llvm::orc::RTDyldObjectLinkingLayer>
	createObjectLinkingLayer(
		llvm::orc::ExecutionSession &ES,
		llvm::orc::JITTargetMachineBuilder &JTMB) {
		auto GetMemoryManager = []() {
			return std::make_unique<
				llvm::SectionMemoryManager>();
		};
		auto OLLayer = std::make_unique<
			llvm::orc::RTDyldObjectLinkingLayer>(
			ES, GetMemoryManager);
\end{lstlisting}

\item COFF对象文件格式稍微有点复杂，它在Windows上使用。这种文件格式不允许将函数标记为导出。这将导致对象链接层内部的检查失败:存储在符号中的标志将与IR中的标志进行比较，这将由于缺少导出标记而导致不匹配。解决方案是只覆盖该文件格式的标志。这样就完成了对象层的构造，并将对象返回给调用者:
\begin{lstlisting}[caption={}]
		if (JTMB.getTargetTriple().isOSBinFormatCOFF()) {
			OLLayer
				->setOverrideObjectFlagsWithResponsibilityFlags(
					true);
			OLLayer
				->setAutoClaimResponsibilityForObjectSymbols(
					true);
		}
		return std::move(OLLayer);
	}
\end{lstlisting}

\item 要初始化编译器层，需要一个IRCompiler实例。IRCompiler实例负责将IR模块编译成目标文件。如果JIT编译器不使用线程，那么可以使用SimpleCompiler类，它使用给定的目标机器来编译IR模块。TargetMachine类不是线程安全的，SimpleCompiler类也是如此。为了支持多线程编译，使用了ConcurrentIRCompiler类，它为每个要编译的模块创建了新的TargetMachine实例。这种方法解决了多线程问题:
\begin{lstlisting}[caption={}]
	static std::unique_ptr<llvm::orc::IRCompileLayer>
	createCompileLayer(
	llvm::orc::ExecutionSession &ES,
	llvm::orc::RTDyldObjectLinkingLayer &OLLayer,
	llvm::orc::JITTargetMachineBuilder JTMB) {
		auto IRCompiler = std::make_unique<
			llvm::orc::ConcurrentIRCompiler>(
			std::move(JTMB));
		auto IRCLayer =
			std::make_unique<llvm::orc::IRCompileLayer>(
				ES, OLLayer, std::move(IRCompiler));
		return std::move(IRCLayer);
	}
\end{lstlisting}

\item 我们不是直接将IR模块编译成机器码，而是首先安装一个优化IR的层。这是一个深思熟虑的设计决策:将JIT编译器转换为优化JIT编译器，这将产生更快的代码，但需要更长的时间来生成，这对用户来说意味着等待。我们没有添加惰性编译，因此当只查找一个符号时，会编译整个模块。在用户看到代码执行之前，这可能需要很长时间。

\begin{tcolorbox}[colback=blue!5!white,colframe=blue!75!black, title=Note]
请注意，引入惰性编译并不是所有情况下的正确解决方案。
\end{tcolorbox}

惰性编译是通过将每个函数移动到它自己的模块中来实现的，该模块在查找函数名时进行编译。这阻止了内联等过程间优化，因为内联传递需要访问为内联而调用的函数体。因此，使用惰性编译，用户会看到更快的启动速度，但生成的代码并不是最优的。这些设计决策取决于预期的用途，我们决定使用快速代码，接受较慢的启动时间。优化层作为转换层实现。IRTransformLayer类将转换委托给一个函数，我们的例子中，会委托给optimizeModule函数:
\begin{lstlisting}[caption={}]
	static std::unique_ptr<llvm::orc::IRTransformLayer>
	createOptIRLayer(
			llvm::orc::ExecutionSession &ES,
			llvm::orc::IRCompileLayer &CompileLayer) {
		auto OptIRLayer =
			std::make_unique<llvm::orc::IRTransformLayer>(
				ES, CompileLayer,
				optimizeModule);
		return std::move(OptIRLayer);
	}
\end{lstlisting}

\item optimizeModule()函数是IR模块上的一个转换示例。该函数获取要转换的模块作为参数，并返回转换后的模块。因为JIT可以使用多个线程运行，IR模块包装在ThreadSafeModule实例中:
\begin{lstlisting}[caption={}]
	static llvm::Expected<llvm::orc::ThreadSafeModule>
	optimizeModule(
		llvm::orc::ThreadSafeModule TSM,
		const llvm::orc::MaterializationResponsibility
			&R) {
\end{lstlisting}

\item 为了优化IR，我们回顾第8章中的一些信息，在向编译器添加优化管道一节。我们需要一个PassBuilder实例来创建一个优化流水。首先，我们定义几个分析管理器，然后在Pass构建器中注册它们。然后，使用O2级别的默认优化流水填充ModulePassManager实例。这又是一个设计决策:O2已经产生了快速的机器码，甚至比O3更快。之后，我们在模块上运行管道。最后，优化后的模块返回给调用者:
\begin{lstlisting}[caption={}]
	TSM.withModuleDo([](llvm::Module &M) {
		bool DebugPM = false;
		llvm::PassBuilder PB(DebugPM);
		llvm::LoopAnalysisManager LAM(DebugPM);
		llvm::FunctionAnalysisManager FAM(DebugPM);
		llvm::CGSCCAnalysisManager CGAM(DebugPM);
		llvm::ModuleAnalysisManager MAM(DebugPM);
		FAM.registerPass(
			[&] { return PB.buildDefaultAAPipeline(); });
		PB.registerModuleAnalyses(MAM);
		PB.registerCGSCCAnalyses(CGAM);
		PB.registerFunctionAnalyses(FAM);
		PB.registerLoopAnalyses(LAM);
		PB.crossRegisterProxies(LAM, FAM, CGAM, MAM);
		llvm::ModulePassManager MPM =
			PB.buildPerModuleDefaultPipeline(
				llvm::PassBuilder::OptimizationLevel::O2,
				DebugPM);
		MPM.run(M, MAM);
	});

	return std::move(TSM);
}
\end{lstlisting}

\item JIT类的客户端需要一种添加IR模块的方法，使用addIRModule()函数提供该模块。记住创建的层堆栈:必须将IR模块添加到顶层，否则会不小心绕过一些层。这将是一个不容易发现的编程错误:如果CompileLayer成员可以替换OptIRLayer成员，那么JIT类仍然可以工作，但不是作为一个优化JIT，因为绕过了这一层。对于这个小的实现，这没有什么值得关注的，但是在一个大型的JIT优化中，需要引入一个函数来返回顶层:
\begin{lstlisting}[caption={}]
	llvm::Error addIRModule(
		llvm::orc::ThreadSafeModule TSM,
		llvm::orc::ResourceTrackerSP RT = nullptr) {
		if (!RT)
			RT = MainJITDylib.getDefaultResourceTracker();
		return OptIRLayer->add(RT, std::move(TSM));
	}
\end{lstlisting}

\item 同样，我们的JIT类的客户端需要一种查找符号的方法，把它委托给ExecutionSession实例，传入一个主符号表的引用和请求符号的内化名称:
\begin{lstlisting}[caption={}]
	llvm::Expected<llvm::JITEvaluatedSymbol>
	lookup(llvm::StringRef Name) {
		return ES->lookup({&MainJITDylib},
							Mangle(Name.str()));
	}
\end{lstlisting}

\end{enumerate}

将JIT编译器组合在一起非常容易。初始化类有点棘手，因为它涉及JIT类的工厂方法和构造函数调用，以及每一层的工厂方法。尽管代码本身很简单，但其是因为C++的限制造成的。\par

下一小节中，我们将使用新的JIT编译器类来实现一个命令行实用程序。\par

\hspace*{\fill} \par %插入空行
\textbf{使用新的JIT编译器类}

我们新的JIT编译器类的接口，类似于(用LLJIT实现我们自己的JIT编译器部分中使用的)LLJIT类。为了测试我们的实现，可以从上一节复制LIT.cpp中的类，并进行以下更改:\par

\begin{enumerate}
\item 为了能够使用我们的新类，我们包含了JIT.h头文件。这将替换llvm/ExecutionEngine/\allowbreak Orc/LLJIT.h头文件，因为这里不再使用LLJIT类，所以不再需要这个头文件。

\item 在jitmain()函数中，用一个新的JIT::create()方法来替换对orc::LLJITBuilder().create()的调用。

\item 同样，在jitmain()函数中，删除了添加DynamicLibrarySearchGenerator类的代码。确切地说，这个生成器集成在JIT类中。
\end{enumerate}

我们可以像上一节一样编译和运行更改后的应用程序，结果是相同的。在内部，新类使用了固定的优化级别，因此对于足够大的模块，可以注意到启动和运行时的差异。\par

手边有一个JIT编译器时，就可以随时验证新的想法。在下一节中，我们将研究如何使用JIT编译器作为静态编译器的一部分，在编译时计算代码。\par













